home *** CD-ROM | disk | FTP | other *** search
/ MacFormat España 24 / MacFormat n. 24 (Spain) / MacFormat 24.bin / Selecciones / WorkinTooHard1.0b1 / Source / WorkinTooHard.p next >
Encoding:
Text File  |  1996-12-07  |  43.9 KB  |  1,415 lines

  1. program WorkinTooHard;
  2.  
  3. (* WorkinTooHard *)
  4. (* ©1996 Quinn "The Eskimo!" *)
  5.  
  6. (* Things to note:
  7.  
  8.     o This program uses the term Tick and Tock in non-obvious ways.  A Tick
  9.         is the basic unit of time, in this case a minute.  A Tock is a collection
  10.         of Ticks, typically about fifteen minutes.  The program wakes up every
  11.         Tick to sample the mouse position to determine whether there has been
  12.         activity.  Every fifteen Ticks, the program Tocks, and records the
  13.         proportion of Ticks that occured with user activity since the last Tock.
  14.         Do not confuse Ticks with the 60th of a second time measurement used
  15.         by the Mac system software.
  16.     
  17.     o Unlike most Mac programs, this one saves it's preferences in its own
  18.         resource fork.  Yeah, I know it's bad style, but it sure makes the code
  19.         easier.  Besides, this is a tiny little desktop utility application,
  20.         so where's the harm in that?
  21. *)
  22.  
  23.     uses
  24.         Types,
  25.         Errors,
  26.         QuickDraw,
  27.         Fonts,
  28.         Windows,
  29.         Menus,
  30.         TextEdit,
  31.         Dialogs,
  32.         Memory,
  33.         Resources,
  34.         GestaltEqu,
  35.         QDOffscreen,
  36.         Devices,
  37.         TextUtils,
  38.         AppleEvents,
  39.         Scrap,
  40.         IntlResources,
  41.         Icons,
  42.         Power;
  43.         
  44.     (* ***** Resource Constants ***** *)
  45.     
  46.     const
  47.         (* MBAR *)
  48.         rMainMenuBar = 128;
  49.         (* WIND *)
  50.         rMainWindow = 128;
  51.         (* RGB# *)
  52.         rColourMap = 128;
  53.         (* MENU *)
  54.         mApple = 128;
  55.             iAbout = 1;
  56.         mFile = 129;
  57.             iSave = 1;
  58.             (* - *)
  59.             iQuit = 3;
  60.         mEdit = 130;
  61.             iUndo = 1;
  62.             (* - *)
  63.             iCut = 3;
  64.             iCopy = 4;
  65.             iPaste = 5;
  66.             iClear = 6;
  67.         mDebug = 131;
  68.             iGrow = 1;
  69.             iShrink = 2;
  70.             (* - *)
  71.             iTimeBackward = 4;
  72.             iTimeForward = 5;
  73.             (* - *)
  74.             iFail = 7;
  75.         (* ALRT *)
  76.         rAboutAlert = 256;
  77.         rInsufficientlyCoolAlert = 257;
  78.         (* PRef *)
  79.         rWindowPosition = 128;
  80.         rSavedActivity = 129;
  81.         (* STR# *)
  82.         rMiscStrings = 128;
  83.             strTimeDiscontinuity = 1;
  84.             strPatience = 2;
  85.             strDeath = 3;
  86.         (* ics# *)
  87.         rSunIcon = 256;
  88.         rMoonIcon = 257;
  89.  
  90.     (* ***** Toolbox Utilities ***** *)
  91.     
  92.     procedure QAssert(mustBeTrue : Boolean);
  93.         forward;
  94.     
  95.     procedure InitToolbox;
  96.     begin
  97.         InitGraf(@qd.thePort);
  98.         InitFonts;
  99.         InitWindows;
  100.         InitMenus;
  101.         TEInit;
  102.         InitDialogs(nil);
  103.         MaxApplZone;
  104.         MoreMasters;
  105.         MoreMasters;
  106.         MoreMasters;
  107.     end; (* InitToolbox *)
  108.  
  109.     function GetWindowContentRegion (theWindow: WindowPtr): RgnHandle;
  110.         (* Returns the window's content region. This is the region currently
  111.             being used, not a copy.  Do not munge it!
  112.         *)
  113.     begin
  114.         GetWindowContentRegion := WindowPeek(theWindow)^.contRgn;
  115.     end; (* GetWindowContentRegion *)
  116.  
  117.     function GetWindowStructureRegion (theWindow: WindowPtr): RgnHandle;
  118.         (* Returns the window's structure region. This is the region currently
  119.             being used, not a copy.  Do not munge it!
  120.         *)
  121.     begin
  122.         GetWindowStructureRegion := WindowPeek(theWindow)^.strucRgn;
  123.     end; (* GetWindowStructureRegion *)
  124.  
  125.     function TitleBarOnScreen (theWindow: WindowPtr): Boolean;
  126.         (* Returns true if the window's title bar is on the screen.
  127.             Note that this routine only works if the window is visible,
  128.             ie you have called ShowWindow on it.  The standard mechanism
  129.             for using this routine is to ShowWindow the window, then
  130.             call TitleBarOnScreen.  If it returns true, everything is cool.
  131.             Otherwise the window is completely off the screen, so you can
  132.             move it back on without causing visible effects.
  133.         *)
  134.         var
  135.             result : Boolean;
  136.             titleBarRegion: RgnHandle;
  137.     begin
  138.         result := true;
  139.         titleBarRegion := NewRgn;
  140.         if titleBarRegion <> nil then begin
  141.             (* First calculate the title bar region by subtracting the content
  142.                 region away from the structure region.
  143.             *)
  144.             CopyRgn(GetWindowStructureRegion(theWindow), titleBarRegion);
  145.             DiffRgn(titleBarRegion, GetWindowContentRegion(theWindow), titleBarRegion);
  146.             
  147.             (* Now intersect the title bar region with the grey region, ie the region
  148.                 describing the extent of the desktop and return true if the intersection
  149.                 is not empty.
  150.             *)
  151.             SectRgn(titleBarRegion, GetGrayRgn, titleBarRegion);
  152.             result := not EmptyRgn(titleBarRegion);
  153.             DisposeRgn(titleBarRegion);
  154.         end; (* if *)
  155.         TitleBarOnScreen := result;
  156.     end; (* TitleBarOnScreen *)
  157.  
  158.     procedure GetWindowRect (theWindow: WindowPtr; var windowRect: Rect);
  159.         (* This routine sets windowRect to the global co-ordinates of 
  160.             the position of the window.  It's typically used for saving window
  161.             state.
  162.         *)
  163.         var
  164.             oldPort : GrafPtr;
  165.     begin
  166.         GetPort(oldPort);
  167.         SetPort(theWindow);
  168.         windowRect := WindowPeek(theWindow)^.port.portRect;
  169.         LocalToGlobal(windowRect.topLeft);
  170.         LocalToGlobal(windowRect.botRight);
  171.         SetPort(oldPort);
  172.     end; (* GetWindowRect *)
  173.     
  174.     function DateTimeToString(dateTime : longint) : Str255;
  175.         (* Returns a string that is the concatination of the
  176.                 date and the time, both derived from the dateTime
  177.                 parameter.
  178.         *) 
  179.         var
  180.             result : Str255;
  181.             tmpStr : Str255;
  182.     begin
  183.         IUDateString(dateTime, abbrevDate, result);
  184.         IUTimeString(dateTime, true, tmpStr);
  185.         result := concat(result, ', ', tmpStr);
  186.         DateTimeToString := result;
  187.     end; (* DateTimeToString *)
  188.     
  189.     function AEGotRequiredParams (theAppleEvent: AppleEvent): OSStatus;
  190.         (* Returns no error if you've extracted all of the required
  191.             parameters out of the AppleEvent.
  192.         *)
  193.         var
  194.             typeCode: DescType;
  195.             actualSize: Size;
  196.             err: OSStatus;
  197.     begin
  198.         err := AEGetAttributePtr(theAppleEvent, keyMissedKeywordAttr, typeWildCard, typeCode, nil, 0, actualSize);
  199.         if err = errAEDescNotFound then begin
  200.             err := noErr;
  201.         end else if err = noErr then begin
  202.             err := errAEEventNotHandled;
  203.         end; (* if *)
  204.         AEGotRequiredParams := err;
  205.     end; (* AEGotRequiredParams *)
  206.  
  207.     function GetShortDayName(dayOfWeek : integer) : Str255;
  208.         (* Parses the international resources to return the
  209.                 abbreviated text representation of the dayOfWeek
  210.                 parameter.
  211.         *)
  212.         type
  213.             Itl1ExtRecPtr = ^Itl1ExtRec;
  214.             Itl1ExtRecHandle = ^Itl1ExtRecPtr;
  215.         var
  216.             itl1H : Handle;
  217.             result : Str255;
  218.             abbrevTableBase : Ptr;
  219.             i : integer;
  220.     begin
  221.         itl1H := GetResource('itl1', GetScriptManagerVariable(smScriptDate));
  222.         QAssert( itl1H <> nil );
  223.         QAssert( GetHandleSize(itl1H) >= sizeof(Itl1ExtRec) );
  224.         
  225.         abbrevTableBase := Ptr( longint(itl1H^) + Itl1ExtRecHandle(itl1H)^^.abbrevDaysTableOffset + sizeof(integer));
  226.         
  227.         for i := 1 to dayOfWeek - 1 do begin
  228.             abbrevTableBase := Ptr( longint(abbrevTableBase) + abbrevTableBase^ + 1 );
  229.         end; (* for *)
  230.         
  231.         BlockMoveData(abbrevTableBase, @result, sizeof(result));
  232.  
  233.         GetShortDayName := result; 
  234.     end; (* GetShortDayName *)
  235.     
  236.     (* ***** Application Types, Constants and Global Data ***** *)
  237.  
  238.     (* ----- Debugging ----- *)
  239.  
  240.     var
  241.         gDebugging : Boolean;
  242.     const
  243.         kOptionKeyCode = 58;
  244.  
  245.     (* gDebugging is a pseudo-constant, established at startup time, that
  246.             enables/disables the built-in debugging in the program.  The value
  247.             is set depending on the state of the kOptionKeyCode at startup
  248.             time. 
  249.     *)    
  250.                 
  251.     (* ----- Time Related Constants ----- *)
  252.  
  253.     var
  254.         gSecondsPerTick : longint;
  255.     const
  256.         kDebuggingSecondsPerTick = 1;
  257.         kStandardSecondsPerTick = 60;
  258.  
  259.     (* gSecondsPerTick is another pseudo-constant that defines the number of
  260.             real seconds that make up a Tick.  At startup time it is set to
  261.             either kDebuggingSecondsPerTick or kStandardSecondsPerTick depending
  262.             on whether we're debugging.  Changing this constant effectively
  263.             makes time run a lot faster, although some weird things happen,
  264.             such as weird placement of the day and night icons.
  265.     *)
  266.  
  267.     const
  268.         kNumberOfDays = 6;
  269.         kTicksPerTock = 15;
  270.         kTocksPerHour = 60 div kTicksPerTock;
  271.         kNumberOfTocks = kNumberOfDays * 24 * kTocksPerHour;
  272.  
  273.     (* The above constants determine important timing parameters for
  274.             the program.  kNumberOfDays is the number of days records
  275.             that the program keeps.  You can change it at will, although
  276.             the window size will start to grow unpleasantly.  kTicksPerTock
  277.             is the number of Ticks in a Tock.  Currently we Tock every
  278.             15 Ticks, ie every quarter hour.  kTocksPerHour is the number
  279.             of Tocks we log per hour.  Note that this constant doesn't factor
  280.             in gSecondsPerTick because we want the changing of gSecondsPerTick
  281.             to accellerate time.  kNumberOfTocks is the total number of Tocks
  282.             that we log.  Ipso facto, it's the number of pixels across in the 
  283.             window.
  284.     *)
  285.     
  286.     const
  287.         kMinTotalTicksForValidInfo = 4;
  288.     (* This is the minimum number of Ticks that we must have taken in order
  289.             to consider the sample valid.
  290.     *)
  291.     
  292.     const
  293.         kTickAccuracyRequirement = 6;
  294.         
  295.     (* gSecondsPerTick div kTickAccuracyRequirement is the bound between
  296.             the current time and the expected time for a Tick to be considered
  297.             accurate.  Ticks that are delivered outside of this bound are inaccurate,
  298.             and don't figure in the activity calculations.
  299.     *)
  300.     
  301.     const
  302.         kManualTimeDisplacementValue = 60;
  303.         
  304.     (* kManualTimeDisplacementValue is the amount of time (in minutes)
  305.             that the debugging time displacement commands add or subtract
  306.             to the current time.
  307.     *)
  308.  
  309.     (* ----- Layout Constants ----- *)
  310.  
  311.     const
  312.         kSourceHeight = 34;
  313.         (* The scrolling display is this many pixels high. *)
  314.     
  315.         kGWorldHExtra = 80;
  316.         (* The number of horizontal pixels that the offscreen GWorld
  317.                 is bigger than the onscreen display.  This allows us
  318.                 to draw off into the offscreen buffer off the edge of the
  319.                 visible display, and smooth scroll the results on to the
  320.                 screen.
  321.         *)
  322.         
  323.         kDestHOffset = 0;
  324.         hDestVOffset = 2;
  325.         (* When drawn in the window, the scrolling display has a border
  326.                 of this many pixels at each edge.
  327.         *)
  328.         
  329.         kGraphVOffset = 18;
  330.         (* The number of pixels from the top of the scrolling display
  331.                 to the base of the activity graph.
  332.         *)
  333.         
  334.         kIconHOffset = -4;
  335.         kIconVOffset = 26;
  336.         (* The offset from the base of the right hand side of the activty
  337.                 graph to the location day/night icons.
  338.         *)
  339.         
  340.         kIconSuiteToDayLabelHOffset = 8;
  341.         kIconSuiteToDayLabelVOffset = 9;
  342.         (* The offset from the bottom right of the night icon to
  343.                 the place where we start drawing the day labels.
  344.         *)
  345.         
  346.         kDefaultWindowFromRight = 60;
  347.         kDefaultWindowFromBottom = 4;
  348.         (* If we don't have a saved window position, we put the window
  349.                 at this offset from the bottom right of the main display.
  350.         *)
  351.         
  352.         kOffscreenGWorldBitDepth = 8;
  353.         (* The pixel depth of the offscreen GWorld. *)
  354.         
  355.         kLabelFontSize = 9;
  356.         (* The font size used to draw the day labels. *)
  357.  
  358.     (* ----- Global Data Structures ----- *)
  359.     
  360.     const
  361.         kActivityMin = 0;
  362.         kActivityMax = kTicksPerTock;
  363.     type
  364.         ActivityValueRange = kActivityMin..kActivityMax;
  365.  
  366.     (* ActivityValueRange defines the range that activity values can fall in to.
  367.             Because there are only kTicksPerTock Ticks in a Tock, it stands to reason
  368.             that the maximum number of activity samples we can have in a Tock is
  369.             kTicksPerTock.
  370.     *)
  371.  
  372.     type        
  373.         ActivityLogType =
  374.             record
  375.                 lastTock : DateTimeRec;
  376.                 recentActivity : array [0..kNumberOfTocks - 1] of
  377.                             record
  378.                                 activityTicks : integer;
  379.                                 totalTicks : integer;
  380.                             end;
  381.             end;
  382.         ActivityLogPtr = ^ActivityLogType;
  383.         ActivityLogHandle = ^ActivityLogPtr;
  384.     
  385.     (* ActivityLogType is the type used to store the recent activity.  Each entry
  386.             in recentActivity is the activity values for a particular Tock.  Entries
  387.             with small indices preceed entries with greater indices in real time.
  388.             lastTock is the real time that the most recent entry was sampled.
  389.     *)
  390.             
  391.     (* ----- General Program Variables ----- *)
  392.  
  393.     var
  394.         gQuitNow : Boolean;                            (* Setting this to true exits the main event loop. *)
  395.  
  396.         gMainWindow : WindowPtr;                (* The main display window. *)
  397.  
  398.         gOffscreenActivityGWorld : GWorldPtr;
  399.                                                                         (* A buffer used to hold the contents of gMainWindow for
  400.                                                                                 quick refresh.
  401.                                                                         *)
  402.  
  403.         gLastMousePos : Point;                    (* The last mouse position we saw -- used to detect
  404.                                                                                 movement and hence user activity.
  405.                                                                         *)
  406.  
  407.     (* ----- Layout Variables ----- *)
  408.  
  409.     var
  410.         gGWorldRect : Rect;                            (* The bounds of gOffscreenActivityGWorld. *)
  411.  
  412.         gSourceRect : Rect;                            (* The source rect used when updating gMainWindow from
  413.                                                                                 gOffscreenActivityGWorld.  Different from gGWorldRect
  414.                                                                                 because gGWorldRect has kGWorldHExtra extra horizontal
  415.                                                                                 pixels to allow for smooth scrolling on to the screen. 
  416.                                                                         *)
  417.  
  418.         gDestRect : Rect;                                (* The destination rect used when updating gMainWindow from
  419.                                                                                 gOffscreenActivityGWorld.  The Grow and Shrink commands
  420.                                                                                 bash this rectangle, and CopyBits does the pixel
  421.                                                                                 expansion work for us.
  422.                                                                         *)
  423.  
  424.         gExpansionFactor : integer;            (* The Grow and Shrink commands record the current
  425.                                                                                 expansion factor here.
  426.                                                                         *)
  427.  
  428.         gActiveValueColourMap : array [ActivityValueRange] of RGBColor;
  429.                                                                         (* Maps activity values into an appropriate colour
  430.                                                                                 We pull this in from a resource to allow for
  431.                                                                                 tweaking.
  432.                                                                         *)
  433.  
  434.         gSunIconSuite : Handle;                    (* An icon suite handle that holds the day icon. *)
  435.         gMoonIconSuite : Handle;                (* An icon suite handle that holds the night icon. *)
  436.  
  437.     (* ----- Global Variables ----- *)
  438.  
  439.     var
  440.         gActivityLog : ActivityLogType;    (* The global record of recent activity. *)
  441.  
  442.         gNeedsSaving : Boolean;                    (* Set to true when gActivityLog needs saving to disk,
  443.                                                                                 cleared when it is saved.  This prevents multiple
  444.                                                                                 sequential Tocks (such as those that happen when
  445.                                                                                 time is discontinuous) from hammering the disk.
  446.                                                                                 It also allows us to defer saving until the
  447.                                                                                 hard disk is spun up on a PowerBook.
  448.                                                                         *)
  449.                                                                         
  450.     (* ----- Time Variables ----- *)
  451.  
  452.     var
  453.         gTimeOffset : longint;                    (* The number of seconds to add to the current dateTime
  454.                                                                                 to yield the virtual dateTime.  This allows the
  455.                                                                                 time distortion debugging commands to distort
  456.                                                                                 time simply.
  457.                                                                         *)
  458.                                                                                 
  459.         gTotalTicks : integer;                    (* Records the number of ticks we've done since
  460.                                                                                 the most recent Tock.
  461.                                                                         *)
  462.  
  463.         gActivityTicks : integer;                (* Records the number of ticks since the last Tock
  464.                                                                                 that we've seen user activity.
  465.                                                                         *)
  466.                                                                         
  467.         gExpectedTickTime : longint;        (* The dateTime for the next expected Tick.
  468.                                                                         *)
  469.  
  470.     (* ***** Simply Application-Specific Routines ***** *)
  471.     
  472.     function GetHourField(dateTime : DateTimeRec) : integer;
  473.         (* An accessor for the hour field of the DateTimeRec.  We
  474.                 use an accessor to allow for accelerated time during
  475.                 debugging.
  476.         *)
  477.     begin
  478.         if gDebugging then begin
  479.             GetHourField := dateTime.minute;
  480.         end else begin
  481.             GetHourField := dateTime.hour;
  482.         end; (* if *)
  483.     end; (* GetHourField *)
  484.         
  485.     function GetMinuteField(dateTime : DateTimeRec) : integer;
  486.         (* An accessor for the minute field of the DateTimeRec.  We
  487.                 use an accessor to allow for accelerated time during
  488.                 debugging.
  489.         *)
  490.     begin
  491.         if gDebugging then begin
  492.             GetMinuteField := dateTime.second;
  493.         end else begin
  494.             GetMinuteField := dateTime.minute;
  495.         end; (* if *)
  496.     end; (* GetMinuteField *)
  497.         
  498.     procedure SetMinuteField(var dateTime : DateTimeRec; minute : integer);
  499.         (* An accessor for the minute field of the DateTimeRec.  We
  500.                 use an accessor to allow for accelerated time during
  501.                 debugging.
  502.         *)
  503.     begin
  504.         if gDebugging then begin
  505.             dateTime.second := minute;
  506.         end else begin
  507.             dateTime.minute := minute;
  508.         end; (* if *)
  509.     end; (* SetMinuteField *)
  510.  
  511.     procedure QAssert(mustBeTrue : Boolean);
  512.         (* Standard assertion routine.  We never expect to trip any assertions
  513.                 in this program.  If we do this routine quits the program in
  514.                 a very abrupt manner (only when running the non-debug version of
  515.                 course).  Note that if this assert trips we bleed a DeathNMRec
  516.                 in the system heap.  I don't consider this a big issues, simply
  517.                 because it should never happen.  Even if it does happen, it's
  518.                 unlikely you'll be doing this enough to notice the bleed.
  519.         *)
  520.         type
  521.             DeathNMRec =
  522.                 record
  523.                     noteRec : NMRec;
  524.                     text : Str255;
  525.                 end;
  526.             DeathNMRecPtr = ^DeathNMRec;
  527.         var
  528.             deathNote : DeathNMRecPtr;
  529.             junk : OSErr;
  530.     begin
  531.         if not mustBeTrue then begin
  532.             if gDebugging then begin
  533.                 DebugStr('QAssert: Assert failed.');
  534.             end else begin
  535.                 deathNote := DeathNMRecPtr( NewPtrSysClear( sizeof(DeathNMRec)) );
  536.                 if deathNote <> nil then begin
  537.                     deathNote^.noteRec.qType := ord(nmType);
  538.                     deathNote^.noteRec.nmStr := @deathNote^.text;
  539.  
  540.                     GetIndString(deathNote^.text, rMiscStrings, strDeath);
  541.                     if deathNote^.text = '' then begin
  542.                         deathNote^.text := 'The application “Working’ Too Hard” encountered an unexpected error and quit.';
  543.                     end; (* if *)
  544.  
  545.                     junk := NMInstall(NMRecPtr(deathNote));
  546.                 end; (* if *)
  547.                 ExitToShell;
  548.             end; (* if *)
  549.         end; (* if *)
  550.     end; (* QAssert *)
  551.     
  552.     function HardDiskSpinning : Boolean;
  553.         (* Returns true if the hard disk is currently powered up.
  554.                 Note that this routine assumes the hard disk is powered
  555.                 if there's no Power Manager available.
  556.         *)
  557.         var
  558.             result : Boolean;
  559.             response : longint;
  560.     begin
  561.         result := true;
  562.         if Gestalt(gestaltPowerMgrAttr, response) = noErr then begin
  563.             if btst(response, gestaltPMgrDispatchExists) then begin
  564.                 if PMSelectorCount >= 6 then begin
  565.                     result := HardDiskPowered;
  566.                 end; (* if *)
  567.             end; (* if *)
  568.         end; (* if *)
  569.         HardDiskSpinning := result;
  570.     end; (* HardDiskSpinning *)
  571.     
  572.     function MyGetDateTime : longint;
  573.         (* A simple wrapper around the system GetDateTime routine.
  574.                 It adds gTimeOffset to the result to allow for 
  575.                 discontinuous time during debugging.
  576.         *)
  577.         var    
  578.             tmp : longint;
  579.     begin
  580.         GetDateTime(tmp);
  581.         tmp := tmp + gTimeOffset;
  582.         MyGetDateTime := tmp;
  583.     end; (* MyGetDateTime *)
  584.     
  585.     procedure SetupExpectedTickTime;
  586.         (* Sets up gExpectedTickTime to point to be the dateTime of the next
  587.                 minute boundary.  Actually, it uses a two minute delay, to avoid
  588.                 having to worry about the current date time wrapper while we're
  589.                 modifying tmpDateTime.
  590.         *)
  591.         var
  592.             tmpDatetime : DateTimeRec;
  593.     begin
  594.         gExpectedTickTime := MyGetDateTime;
  595.         SecondsToDate(gExpectedTickTime, tmpDatetime);
  596.         tmpDatetime.second := 0;
  597.         SetMinuteField(tmpDatetime, GetMinuteField(tmpDatetime) + 2);
  598.         DateToSeconds(tmpDateTime, gExpectedTickTime);
  599.     end; (* SetupExpectedTickTime *)
  600.  
  601.     procedure CalculateWindowRect(var windowRect : Rect);
  602.         (* Given the basic destination rectangle size specified by windowRect
  603.              (whose topLeft co-ordinate must be 0,0), calculate the gDestRect
  604.              and return windowRect as the rectangle of the enclosing window
  605.              (ie around gDestRect and the border around it).
  606.         *)
  607.     begin    
  608.         gDestRect := windowRect;
  609.         OffsetRect(gDestRect, kDestHOffset, hDestVOffset);
  610.  
  611.         InsetRect(windowRect, -kDestHOffset, -hDestVOffset);
  612.         
  613.         OffsetRect(windowRect,
  614.                     qd.screenBits.bounds.right - (windowRect.right - windowRect.left + kDefaultWindowFromRight),
  615.                     qd.screenBits.bounds.bottom - (windowRect.bottom - windowRect.top + kDefaultWindowFromBottom)
  616.                     );
  617.     end; (* CalculateWindowRect *)
  618.     
  619.     function SystemActive : Boolean;
  620.         (* Returns true if the machine is actively being used by a human (or
  621.                 something that's close to a human).  It detects mouse movements
  622.                 by comparing the mouse position to gLastMousePos, which is also
  623.                 updates.  It also attempt to detect key presses by peeking
  624.                 in the OS event queue.  The key detection is pretty feeble, and
  625.                 I may need to revise this routine to do a better job.  [Possible
  626.                 a jGNEFilter, but I'll consult with others before doing that.]
  627.         *)
  628.         var
  629.             result : Boolean;
  630.             event : EventRecord;
  631.             currentMousePos : Point;
  632.             junkBool : Boolean;
  633.     begin
  634.         junkBool := OSEventAvail(0, event);
  635.         currentMousePos := event.where;
  636.         result := (currentMousePos.h <> gLastMousePos.h) or (currentMousePos.v <> gLastMousePos.v);
  637.         if not result then begin
  638.             result := OSEventAvail(keyDownMask + keyUpMask + autoKeyMask, event);
  639.         end; (* if *)
  640.         gLastMousePos := currentMousePos;
  641.         SystemActive := result;
  642.     end; (* SystemActive *)
  643.     
  644.     procedure UpdateMainWindow;
  645.         (* Updates the main window from gOffscreenActivityGWorld. *)
  646.     begin
  647.         SetPort(gMainWindow);
  648.         CopyBits( GrafPtr(gOffscreenActivityGWorld)^.portBits, gMainWindow^.portBits,
  649.                             gSourceRect, gDestRect, srcCopy, nil);
  650.     end; (* UpdateMainWindow *)
  651.     
  652.     procedure FlushApplicationVolume;
  653.         (* Flushes the volume that contains the application.  It does
  654.                 this by looking up the FCB for the application's resource
  655.                 fork to find the application's volume's vRefNum.
  656.         *)
  657.         var
  658.             fcbPB : FCBPBRec;
  659.             junkName : Str255;
  660.     begin
  661.         fcbPB.ioNamePtr := @junkName;
  662.         fcbPB.ioVRefNum := 0;
  663.         fcbPB.ioRefNum := CurResFile;
  664.         fcbPB.ioFCBIndx := 0;
  665.         QAssert( PBGetFCBInfoSync(@fcbPB) = noErr);
  666.         QAssert( FlushVol(nil, fcbPB.ioVRefNum) = noErr);
  667.     end; (* FlushApplicationVolume *)
  668.     
  669.     (* ***** Core Implementation ***** *)
  670.             
  671.     procedure SaveActivityLog;
  672.         (* Saves gActivityLog to a resource in the application's resource fork. *)
  673.         var
  674.             savedActivity : Handle;
  675.     begin
  676.         savedActivity := Get1Resource('PRef', rSavedActivity);
  677.         if savedActivity = nil then begin
  678.             savedActivity := NewHandle(sizeof(gActivityLog));
  679.             QAssert( savedActivity <> nil );
  680.             AddResource(savedActivity, 'PRef', rSavedActivity, '');
  681.         end else begin
  682.             SetHandleSize(savedActivity, sizeof(gActivityLog));
  683.         end; (* if *)
  684.  
  685.         QAssert(savedActivity <> nil);
  686.         QAssert( GetHandleSize(savedActivity) = sizeof(gActivityLog) );
  687.  
  688.         BlockMoveData(@gActivityLog, savedActivity^, sizeof(gActivityLog));
  689.         ChangedResource(savedActivity);
  690.         UpdateResFile(CurResFile);
  691.         FlushApplicationVolume;
  692.  
  693.         gNeedsSaving := false;
  694.     end; (* SaveActivityLog *)
  695.     
  696.     procedure ClearActivityLog;
  697.         (* Clears the global activity log, and refreshes the display. *)
  698.         var
  699.             oldWorld : GrafPtr;
  700.             tmpStr : Str255;
  701.             i : integer;
  702.     begin
  703.         GetPort(oldWorld);
  704.  
  705.         (* First, clear the offscreen world, and write an explanatory message. *)
  706.         SetPort(GrafPtr(gOffscreenActivityGWorld));
  707.         EraseRect(gGWorldRect);
  708.         GetIndString(tmpStr, rMiscStrings, strTimeDiscontinuity);
  709.         TextFace([italic]);
  710.         TETextBox(@tmpStr[1], length(tmpStr), gSourceRect, teJustRight);
  711.         TextFace([]);
  712.         
  713.         (* Now force an update of the onscreen world. *)
  714.         SetPort(gMainWindow);
  715.         InvalRect(gDestRect);
  716.         SetPort(oldWorld);
  717.  
  718.         (* Finally, clear gActivityLog. *)
  719.         for i := 0 to kNumberOfTocks - 1 do begin
  720.             gActivityLog.recentActivity[i].activityTicks := 0;
  721.             gActivityLog.recentActivity[i].totalTicks := 0;
  722.         end; (* for *)
  723.         SetupExpectedTickTime;
  724.  
  725.         gNeedsSaving := true;
  726.     end; (* ClearActivityLog *)
  727.     
  728.     procedure Tock(activityTicks : integer; totalTicks : integer; tockTime : DateTimeRec; redraw : Boolean);
  729.         (* Tock is called every fifteen minutes and represents the true core of
  730.                 the implementation.  The activityTicks and totalTicks represent the
  731.                 data to be logged for this Tock.  The tockTime parameter is the time
  732.                 at which the Tock happened (or was scheduled to happen), as distinct
  733.                 from the current time.  The redraw parameter tells the routine whether
  734.                 to redraw immediately or not.
  735.              Some non-obvious things:
  736.              
  737.              o I update the on-screen image by copying the entire offscreen image.
  738.                   I could call ScrollRect on the on-screen image, but this is tricky
  739.                   if there is a pending update event.  Anyway, ScrollRect is simply
  740.                   a special case for CopyBits, so I might as well CopyBits it from
  741.                   offscreen -- it may even be faster (because it's non-overlapping).
  742.              
  743.              o The marker drawing code looks for (minute = kTicksPerTock), rather
  744.                   than (minute = 0).  This is because when we're called at (minute = 0),
  745.                   the actual data (ie activityTicks and totalTicks) is for the previous
  746.                   fifteen minutes.  Rather than mess with the calling sequence, I adjust
  747.                   for it here.
  748.         *)
  749.         var
  750.             i : integer;
  751.             activityInfoValid : Boolean;
  752.             scaledActivity : integer;
  753.             oldWorld : GrafPtr;
  754.             junkRgn : RgnHandle;
  755.             oldColour : RGBColor;
  756.             sicnRect : Rect;
  757.             minute : integer;
  758.             hour : integer;
  759.     begin
  760.         {$ifc false}
  761.             DebugStr(stringof('Tock: activityTicks = ', activityTicks:1, ', totalTicks = ', totalTicks:1, ' ; g'));
  762.         {$endc}
  763.         gNeedsSaving := true;
  764.  
  765.         (* Log the raw activity information. *)        
  766.         for i := 1 to kNumberOfTocks - 1 do begin
  767.             gActivityLog.recentActivity[i - 1] := gActivityLog.recentActivity[i];
  768.         end; (* for *)
  769.         gActivityLog.recentActivity[kNumberOfTocks - 1].activityTicks := activityTicks;
  770.         gActivityLog.recentActivity[kNumberOfTocks - 1].totalTicks := totalTicks;
  771.         gActivityLog.lastTock := tockTime;
  772.  
  773.         (* Process the activity information to yield scaledActivity, which
  774.                 will be the height of the graph for this tock.
  775.         *)
  776.         activityInfoValid := (totalTicks >= kMinTotalTicksForValidInfo);
  777.         if activityInfoValid then begin
  778.             scaledActivity := (activityTicks  * kActivityMax) div totalTicks;
  779.         end else begin
  780.             scaledActivity := 0;
  781.         end; (* if *)
  782.         QAssert( (scaledActivity >= kActivityMin) & (scaledActivity <= kActivityMax) );
  783.         
  784.         junkRgn := NewRgn;
  785.         QAssert(junkRgn <> nil);
  786.         GetPort(oldWorld);
  787.  
  788.         (* Switch to the offscreen world, and scroll it left one pixel. *)
  789.         SetPort(GrafPtr(gOffscreenActivityGWorld));
  790.         ScrollRect(gGWorldRect, -1, 0, junkRgn);
  791.  
  792.         (* Now draw any new info in the offscreen world. *)
  793.         GetForeColor(oldColour);
  794.         if activityInfoValid then begin
  795.  
  796.             (* Draw the base line. *)
  797.             MoveTo(gSourceRect.right - 1, kGraphVOffset);
  798.             Line(0, 0);
  799.  
  800.             (* Draw the bar height. *)
  801.             RGBForeColor( gActiveValueColourMap[scaledActivity] );
  802.             MoveTo(gSourceRect.right - 1, kGraphVOffset - 1);
  803.             Line(0, -scaledActivity);
  804.         end; (* if *)
  805.         RGBForeColor(oldColour);
  806.         
  807.         hour := GetHourField(tockTime);
  808.         minute := GetMinuteField(tockTime);
  809.  
  810.         (* Now draw the hour marker. *)
  811.         if minute = kTicksPerTock then begin
  812.             MoveTo(gSourceRect.right - 1, kGraphVOffset + 1);
  813.             Line(0, 1);
  814.         end; (* if *)
  815.         
  816.         (* Now draw the day marker. *)
  817.         if ((hour = 0) or (hour = 12)) and (minute = kTicksPerTock) then begin
  818.             MoveTo(gSourceRect.right - 1, kGraphVOffset + 3);
  819.             Line(0, 1);
  820.         end; (* if *)
  821.         
  822.         (* Draw the day/night icons. *)
  823.         if ((hour = 0) or (hour = 12)) and (minute = kTicksPerTock) then begin
  824.             SetRect(sicnRect, 0, 0, 16, 16);
  825.             OffsetRect(sicnRect, gSourceRect.right + kIconHOffset, kIconVOffset);
  826.             if hour = 0 then begin
  827.                 QAssert( PlotIconSuite(sicnRect, kAlignNone, kTransformNone, gMoonIconSuite) = noErr);
  828.                 MoveTo(sicnRect.right - kIconSuiteToDayLabelHOffset, sicnRect.bottom - kIconSuiteToDayLabelVOffset);
  829.                 DrawString(GetShortDayName(tockTime.dayOfWeek));
  830.             end else begin
  831.                 QAssert( PlotIconSuite(sicnRect, kAlignNone, kTransformNone, gSunIconSuite) = noErr);
  832.             end; (* if *)
  833.         end; (* if *)
  834.     
  835.         (* If this is an immediate operation, redraw the main window from the offscreen GWorld. *)
  836.         if redraw then begin
  837.             UpdateMainWindow;
  838.             ValidRect(gDestRect);
  839.         end; (* if *)
  840.  
  841.         (* Clean up. *)
  842.         SetPort(oldWorld);
  843.         DisposeRgn(junkRgn);
  844.     end; (* Tock *)
  845.  
  846.     procedure Tick(currentTime : longint);
  847.         (* This routine is called every item the currentTime exceeds gExpectedTickTime.  The
  848.                 routine also updates gExpectedTickTime by gSecondsPerTick.  The net effect is that
  849.                 this routine is called roughly every minute.
  850.         *)
  851.         var
  852.             tmpDateRec : DateTimeRec;
  853.     begin
  854.         (* Collect activity statistics.  If currentTime is close enough to gExpectedTickTime,
  855.                 we note that we have a new sample by bumping gTotalTicks.  If the system is
  856.                 active, we also bump gActivityTicks.
  857.         *)
  858.         if abs(currentTime - gExpectedTickTime) < ((gSecondsPerTick div kTickAccuracyRequirement) + 1) then begin
  859.             if SystemActive then begin
  860.                 gActivityTicks := gActivityTicks + 1;
  861.             end; (* if *)
  862.             gTotalTicks := gTotalTicks + 1;
  863.         end; (* if *)
  864.  
  865.         (* Now check whether we've hit a fifteen minute boundary.  If so,
  866.                 call Tock and then reset the counters.
  867.         *)
  868.         SecondsToDate(gExpectedTickTime, tmpDateRec);
  869.         if (GetMinuteField(tmpDateRec) mod kTicksPerTock) = 0 then begin
  870.             Tock(gActivityTicks, gTotalTicks, tmpDateRec, true);
  871.             gTotalTicks := 0;
  872.             gActivityTicks := 0;
  873.         end; (* if *)
  874.         
  875.         (* Finally bump gExpectedTickTime, so that we get called back next minute. *)
  876.         gExpectedTickTime := gExpectedTickTime + gSecondsPerTick;
  877.     end; (* Tick *)
  878.  
  879.     (* ***** AppleEvent Handling ***** *)
  880.         
  881.     function HandleAEOpenApplication (var theAppleEvent : AppleEvent; var reply: AppleEvent; refcon : longint): OSErr;
  882.         (* Handle the Open Application AppleEvent, which does not a lot.
  883.         *)
  884.         var
  885.             err: OSStatus;
  886.     begin
  887.         {$unused reply}
  888.         {$unused refcon}
  889.         err := AEGotRequiredParams(theAppleEvent);
  890.         if err = noErr then begin
  891.             err := noErr;
  892.         end; (* if *)
  893.         HandleAEOpenApplication := err;
  894.     end; (* HandleAEOpenApplication *)
  895.  
  896.     function HandleAEQuitApplication (var theAppleEvent, reply: AppleEvent; refcon : longint): OSErr;
  897.         (* Handle the Quit AppleEvent.  Simple sets gQuitNow.
  898.         *)
  899.         var
  900.             err: OSStatus;
  901.     begin
  902.         {$unused reply}
  903.         {$unused refcon}
  904.         err := AEGotRequiredParams(theAppleEvent);
  905.         if err = noErr then begin
  906.             gQuitNow := true;
  907.         end; (* if *)
  908.         HandleAEQuitApplication := err;
  909.     end; (* HandleAEQuitApplication *)
  910.  
  911.     (* ***** Menu Handling ***** *)
  912.  
  913.     procedure AdjustMenu;
  914.         (* Adjust the various menus in preparation for a handling a menu operation.
  915.                 Note that this deliberately doesn't adjust the Debug menu because that
  916.                 menu is for debugging, and I don't really care whether it's adjusted
  917.                 properly.
  918.         *)
  919.         var
  920.             editMenuH : MenuHandle;
  921.             fileMenuH : MenuHandle;
  922.     begin
  923.         (* Edit *)
  924.         editMenuH := GetMenuHandle(mEdit);
  925.         QAssert( editMenuH <> nil );
  926.         if FrontWindow = gMainWindow then begin
  927.             DisableItem(editMenuH, iUndo);
  928.             DisableItem(editMenuH, iCut);
  929.             EnableItem(editMenuH, iCopy);
  930.             DisableItem(editMenuH, iPaste);
  931.             DisableItem(editMenuH, iClear);
  932.         end else begin
  933.             EnableItem(editMenuH, iUndo);
  934.             EnableItem(editMenuH, iCut);
  935.             EnableItem(editMenuH, iCopy);
  936.             EnableItem(editMenuH, iPaste);
  937.             EnableItem(editMenuH, iClear);
  938.         end; (* if *)
  939.         
  940.         (* File *)
  941.         fileMenuH := GetMenuHandle(mFile);
  942.         QAssert( fileMenuH <> nil );
  943.         if gNeedsSaving then begin
  944.             EnableItem(fileMenuH, iSave);
  945.         end else begin
  946.             DisableItem(fileMenuH, iSave);
  947.         end; (* if *)
  948.     end; (* AdjustMenu *)
  949.     
  950.     procedure DoMenu(menuItem : longint);
  951.         (* Act is response to a menu command.  Most of this is standard menu handling
  952.                 stuff, with a few real operations mixed in.
  953.         *)
  954.         var
  955.             menu : integer;
  956.             item : integer;
  957.             daName : Str255;
  958.             junk : integer;
  959.             windowRect : Rect;
  960.             newPict : PicHandle;
  961.             junkLong : longint;
  962.             versH : VersRecHndl;
  963.             startTicks : longint;
  964.     begin
  965.         startTicks := TickCount;
  966.         menu := hiwrd(menuItem);
  967.         item := lowrd(menuItem);
  968.         case menu of
  969.             mApple:
  970.                 if item = iAbout then begin
  971.                     versH := VersRecHndl(Get1Resource('vers', 1));
  972.                     QAssert(versH <> nil);
  973.                     ParamText(versH^^.shortVersion, '', '', '');
  974.                     junk := Alert(rAboutAlert, nil);
  975.                 end else begin
  976.                     GetMenuItemText(GetMenuHandle(mApple), item, daName);
  977.                     junk := OpenDeskAcc(daName);
  978.                 end; (* if *)
  979.             mFile:
  980.                 case item of
  981.                     iSave:
  982.                         begin
  983.                             QAssert( gNeedsSaving );
  984.                             SaveActivityLog;
  985.                         end;
  986.                     iQuit:
  987.                         gQuitNow := true;
  988.                     otherwise
  989.                         (* do nothing *) ;
  990.                 end; (* case *)
  991.             mEdit:
  992.                 case item of
  993.                     iCopy :
  994.                         begin
  995.                             (* Create a picture of the main window, and put it into the scrap. *)
  996.                             SetPort(gMainWindow);
  997.                             newPict := OpenPicture(gDestRect);
  998.                             QAssert( newPict <> nil );
  999.                             UpdateMainWindow;
  1000.                             ClosePicture;
  1001.                             junkLong := ZeroScrap;
  1002.                             HLock(Handle(newPict));
  1003.                             junkLong := PutScrap(GetHandleSize(Handle(newPict)), 'PICT', newPict^);
  1004.                             KillPicture(newPict);
  1005.                         end;
  1006.                     otherwise
  1007.                         (* do nothing *) ;
  1008.                 end; (* case *)
  1009.             mDebug:
  1010.                 case item of
  1011.                     iGrow, iShrink:
  1012.                         begin
  1013.                             (* Grow or shrink the main window, adjusting gDestRect so that
  1014.                                     the offscreen world is stretched when it's redrawn.
  1015.                             *)
  1016.                             if item = iGrow then begin
  1017.                                 gExpansionFactor := gExpansionFactor * 2;
  1018.                             end else begin
  1019.                                 gExpansionFactor := gExpansionFactor div 2;
  1020.                                 if gExpansionFactor < 1 then begin
  1021.                                     gExpansionFactor := 1;
  1022.                                 end; (* if *)
  1023.                             end; (* if *)
  1024.                             windowRect := gSourceRect;
  1025.                             windowRect.top := windowRect.top * gExpansionFactor;
  1026.                             windowRect.bottom := windowRect.bottom * gExpansionFactor;
  1027.                             windowRect.left := windowRect.left * gExpansionFactor;
  1028.                             windowRect.right := windowRect.right * gExpansionFactor;
  1029.                             
  1030.                             CalculateWindowRect(windowRect);
  1031.  
  1032.                             SizeWindow(gMainWindow,
  1033.                                                     windowRect.right - windowRect.left,
  1034.                                                     windowRect.bottom - windowRect.top,
  1035.                                                     false);
  1036.  
  1037.                             (* If the window gets too big, place it on the screen so we can
  1038.                                     see the right hand side.
  1039.                             *)
  1040.                             if (windowRect.right - windowRect.left) > (qd.screenBits.bounds.right - qd.screenBits.bounds.left) then begin
  1041.                                 MoveWindow(gMainWindow, qd.screenBits.bounds.right - (windowRect.right - windowRect.left + 20) - 4, 40, true);
  1042.                             end; (* if *)
  1043.  
  1044.                             SetPort(gMainWindow);
  1045.                             EraseRect(WindowPeek(gMainWindow)^.port.portRect);
  1046.                             InvalRect(gDestRect);
  1047.                         end;
  1048.                     iTimeBackward:
  1049.                         gTimeOffset := gTimeOffset - (gSecondsPerTick * kManualTimeDisplacementValue);
  1050.                     iTimeForward:
  1051.                         gTimeOffset := gTimeOffset + (gSecondsPerTick * kManualTimeDisplacementValue);
  1052.                     iFail:
  1053.                         begin
  1054.                             gDebugging := false;
  1055.                             QAssert(false);
  1056.                         end
  1057.                     otherwise
  1058.                         (* do nothing *) ;
  1059.                 end; (* case *)                
  1060.             otherwise
  1061.                 (* do nothing *)
  1062.         end; (* case *)
  1063.         
  1064.         if not gQuitNow then begin
  1065.             (* Make sure that all menu commands (except Quit) take at least 10 ticks,
  1066.                     so that the user can actually see the menu title flash when using
  1067.                     command keys.
  1068.             *)
  1069.             while TickCount < startTicks + 10 do begin
  1070.                 Delay(1, junkLong);
  1071.             end; (* while *)
  1072.             HiliteMenu(0);
  1073.         end; (* if *)
  1074.     end; (* DoMenu *)
  1075.  
  1076.     (* ***** Low-Level Event Handling ***** *)
  1077.  
  1078.     procedure DoDragWindow(hitWindow : WindowPtr; event : EventRecord);
  1079.         (* Respond to a mouse down inDrag or inContent.  Calls DragWindow
  1080.                 then saves then remembers the position in a resource.
  1081.         *)
  1082.         var
  1083.             currentPosition : Rect;
  1084.             windowPos : Handle;
  1085.     begin
  1086.         DragWindow(hitWindow, event.where, qd.screenBits.bounds);
  1087.         if hitWindow = gMainWindow then begin
  1088.             GetWindowRect(gMainWindow, currentPosition);
  1089.             windowPos := Get1Resource('PRef', rWindowPosition);
  1090.             if windowPos = nil then begin
  1091.                 windowPos := NewHandle(sizeof(Point));
  1092.                 QAssert(windowPos <> nil);
  1093.                 AddResource(windowPos, 'PRef', rWindowPosition, '');
  1094.             end else begin
  1095.                 SetHandleSize(windowPos, sizeof(Point));
  1096.             end; (* if *)
  1097.             PointPtr(windowPos^)^ := currentPosition.topLeft;
  1098.             ChangedResource(windowPos);
  1099.             UpdateResFile(CurResFile);
  1100.             FlushApplicationVolume;
  1101.         end; (* if *)
  1102.     end; (* DoDragWindow *)
  1103.  
  1104.     procedure HandleEvent(event : EventRecord);
  1105.         (* Handle a user interface event.  Standard event loop fodder. *)
  1106.         var
  1107.             hitWindow : WindowPtr;
  1108.             junk : OSErr;
  1109.     begin
  1110.         case event.what of
  1111.             updateEvt:
  1112.                 begin
  1113.                     hitWindow := WindowPtr(event.message);
  1114.                     BeginUpdate(hitWindow);
  1115.                         if hitWindow = gMainWindow then begin
  1116.                             UpdateMainWindow;
  1117.                         end; (* if *)
  1118.                     EndUpdate(hitWindow);
  1119.                 end;
  1120.             mouseDown:
  1121.                 case FindWindow(event.where, hitWindow) of
  1122.                     inMenuBar:
  1123.                         begin
  1124.                             AdjustMenu;
  1125.                             DoMenu(MenuSelect(event.where));
  1126.                         end;
  1127.                     inDrag, inContent:
  1128.                         DoDragWindow(hitWindow, event);
  1129.                     otherwise
  1130.                         (* do nothing *)
  1131.                 end; (* case *)
  1132.             keyDown:
  1133.                 if band(event.modifiers, cmdKey) <> 0 then begin
  1134.                     AdjustMenu;
  1135.                     DoMenu(MenuKey(chr(band(event.message, charCodeMask))));
  1136.                 end else begin
  1137.                     SysBeep(10);
  1138.                 end; (* if *)
  1139.             nullEvent:
  1140.                 (* do nothing *) ;
  1141.             kHighLevelEvent:
  1142.                 junk := AEProcessAppleEvent(event);
  1143.         end; (* case *)
  1144.     end; (* HandleEvent *)
  1145.  
  1146.     (* ***** Initialisation ***** *)
  1147.         
  1148.     procedure InitApplication;
  1149.         (* Initialise all the application's global state.  Lots of code here! *)
  1150.         var
  1151.             response : longint;
  1152.             sysEnv : SysEnvRec;
  1153.             junk : integer;
  1154.             versH : VersRecHndl;
  1155.             km : KeyMap;
  1156.             rgbResource : Handle;
  1157.             windowRect : Rect;
  1158.             mbar : Handle;
  1159.             debugMenu : MenuHandle;
  1160.             oldWorld : GrafPtr;
  1161.             windowPos : Handle;
  1162.             savedPosition : Point;
  1163.             originalPosition : Rect;
  1164.             tmpStr : Str255;
  1165.             savedActivity : Handle;
  1166.             tmpDatetime : DateTimeRec;
  1167.             i : integer;
  1168.     begin
  1169.         (* Check Environment -- Make sure we have System 7 or greater and Colour QuickDraw. *)
  1170.         if (Gestalt(gestaltSystemVersion, response) <> noErr) |
  1171.                 (response < $0700) |
  1172.                 (SysEnvirons(curSysEnvVers, sysEnv) <> noErr) |
  1173.                 not sysEnv.hasColorQD then begin
  1174.             junk := StopAlert(rInsufficientlyCoolAlert, nil);
  1175.             ExitToShell;
  1176.         end; (* if *)
  1177.         
  1178.         (* Debugging -- Set gDebugging to true, unless the 'vers' resource indicates
  1179.                 that this is a final version.  Toggle whatever setting we have if the option
  1180.                 key is down.
  1181.         *)
  1182.         
  1183.         gDebugging := true;
  1184.         versH := VersRecHndl(Get1Resource('vers', 1));
  1185.         if versH <> nil then begin
  1186.             gDebugging := (versH^^.numericVersion.stage < betaStage);
  1187.         end; (* if *)
  1188.         GetKeys(km);
  1189.         if km[kOptionKeyCode] then begin
  1190.             gDebugging := not gDebugging;
  1191.         end; (* if *)
  1192.         
  1193.         (* 'Constants' -- Set up gSecondsPerTick for either fast or slow time
  1194.                 depending on whether we're debugging or not.
  1195.         *)
  1196.  
  1197.         if gDebugging then begin
  1198.             gSecondsPerTick := kDebuggingSecondsPerTick;
  1199.         end else begin
  1200.             gSecondsPerTick := kStandardSecondsPerTick;
  1201.         end; (* if *)
  1202.  
  1203.         (* Layout -- Set up all the rectangles needed to draw the graph,
  1204.                 along with some constant data structures for icons and such.
  1205.         *)
  1206.  
  1207.         gSourceRect.top := 0;
  1208.         gSourceRect.left := 0;
  1209.         gSourceRect.bottom := kSourceHeight;
  1210.         gSourceRect.right := kNumberOfTocks;
  1211.         
  1212.         windowRect := gSourceRect;
  1213.         CalculateWindowRect(windowRect);            (* Also sets up gDestRect. *)
  1214.  
  1215.         gGWorldRect := gSourceRect;
  1216.         gGWorldRect.right := gGWorldRect.right + kGWorldHExtra;
  1217.  
  1218.         gExpansionFactor := 1;
  1219.  
  1220.         rgbResource := Get1Resource('RGB#', rColourMap);
  1221.         QAssert( rgbResource <> nil);
  1222.         QAssert( GetHandleSize(rgbResource) = sizeof(gActiveValueColourMap) );
  1223.         BlockMoveData(rgbResource^, @gActiveValueColourMap, sizeof(gActiveValueColourMap));
  1224.  
  1225.         QAssert( GetIconSuite(gSunIconSuite, rSunIcon, kSelectorAllSmallData) = noErr);
  1226.         QAssert( gSunIconSuite <> nil);
  1227.         QAssert( GetIconSuite(gMoonIconSuite, rMoonIcon, kSelectorAllSmallData) = noErr);
  1228.         QAssert( gMoonIconSuite <> nil);
  1229.  
  1230.         (* Menus -- Create the menu bar. *)
  1231.                 
  1232.         mbar := GetNewMBar(rMainMenuBar);
  1233.         QAssert(mbar <> nil);
  1234.         SetMenuBar(mbar);
  1235.         if gDebugging then begin
  1236.             debugMenu := GetMenu(mDebug);
  1237.             QAssert( debugMenu <> nil );
  1238.             InsertMenu(debugMenu, 0);
  1239.         end; (* if *)
  1240.         AppendResMenu(GetMenuHandle(mApple), 'DRVR');
  1241.         DrawMenuBar;
  1242.  
  1243.         (* AppleEvent Handlers -- Install our AppleEvent handlers. *)
  1244.         
  1245.         QAssert( AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
  1246.                                     NewAEEventHandlerProc(HandleAEOpenApplication), 0, false) = noErr);
  1247.         QAssert( AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, 
  1248.                                     NewAEEventHandlerProc(HandleAEQuitApplication), 0, false) = noErr);
  1249.  
  1250.         (* General -- Set up the general variables, including creating the window
  1251.                 and offscreen buffer.
  1252.         *)
  1253.         
  1254.         gQuitNow := false;
  1255.         gMainWindow := NewCWindow(nil, windowRect, '', false, altDBoxProc, WindowPtr(-1), false, 0);
  1256.         QAssert(gMainWindow <> nil);
  1257.  
  1258.         (* Restore the window position from a resource.  Move it back on screnn if
  1259.                 necessary.
  1260.         *)
  1261.         windowPos := Get1Resource('PRef', rWindowPosition);
  1262.         if windowPos <> nil then begin
  1263.             savedPosition := PointPtr(windowPos^)^;
  1264.  
  1265.             GetWindowRect(gMainWindow, originalPosition);
  1266.             MoveWindow(gMainWindow, savedPosition.h, savedPosition.v, false);
  1267.             ShowWindow(gMainWindow);
  1268.             if not TitleBarOnScreen(gMainWindow) then begin
  1269.                 MoveWindow(gMainWindow, originalPosition.left, originalPosition.top, false);
  1270.             end; (* if *)
  1271.         end else begin
  1272.             ShowWindow(gMainWindow);        
  1273.         end; (* if *)
  1274.  
  1275.         (* Create and set up the offscreen GWorld. *)        
  1276.         QAssert( NewGWorld(gOffscreenActivityGWorld, kOffscreenGWorldBitDepth, gGWorldRect, nil, nil, 0) = noErr);
  1277.         GetPort(oldWorld);
  1278.         SetPort(GrafPtr(gOffscreenActivityGWorld));
  1279.         EraseRect(gGWorldRect);
  1280.         TextFont(applFont);
  1281.         TextSize(kLabelFontSize);
  1282.         
  1283.         gLastMousePos.h := -1;
  1284.         gLastMousePos.v := -1;
  1285.  
  1286.         (* Time *)
  1287.         
  1288.         gActivityTicks := 0;
  1289.         gTotalTicks := 0;
  1290.         gTimeOffset := 0;
  1291.  
  1292.         (* Global State *)
  1293.  
  1294.         (* Get the saved info out of a resource. *)
  1295.         savedActivity := Get1Resource('PRef', rSavedActivity);
  1296.         if savedActivity <> nil then begin
  1297.             if GetHandleSize(savedActivity) = sizeof(gActivityLog) then begin
  1298.                 HLock(savedActivity);
  1299.             end else begin
  1300.                 savedActivity := nil;
  1301.             end; (* if *)
  1302.         end; (* if *)
  1303.         
  1304.         (* Initialise the activity log in one of two ways, depending on whether
  1305.                 we have saved info or not.
  1306.         *)
  1307.         if savedActivity = nil then begin
  1308.             (* Start time anew -- relies on the fact that gActivityLog is initialised to zero
  1309.                     by the environment.
  1310.             *)
  1311.             SetupExpectedTickTime;
  1312.             GetIndString(tmpStr, rMiscStrings, strPatience);
  1313.             TextFace([italic]);
  1314.             TETextBox(@tmpStr[1], length(tmpStr), gSourceRect, teJustLeft);
  1315.             TextFace([]);
  1316.         end else begin
  1317.             (* Use the existing activity log.  This is a really horrible chunk of code.
  1318.                     The basic problem is that we have to feed data into the gActivityLog,
  1319.                     however we can't just overwrite it because then we'd have to redraw
  1320.                     the stuff in the offscreen world.  So instead we feed the data into
  1321.                     the activity log one sample at a time by calling Tock.  
  1322.                  To do this, we need to calculate the time associated with each sample.
  1323.                     However we only remember the time of the last sample (ie lastTock),
  1324.                     so we have to calculate the time for each of the previous sample
  1325.                     by extrapolating back from lastTock.  Fortunately we know that each
  1326.                     Tock is separated by 15 minutes, so we can work out the time of
  1327.                     the first tock.
  1328.             *)
  1329.             
  1330.             (* Calculate the time of the first saved Tock by converting lastTock to
  1331.                     seconds, then subtracting away fifteen minutes for each kNumberOfTocks.
  1332.             *)
  1333.             DateToSeconds( ActivityLogHandle(savedActivity)^^.lastTock, gExpectedTickTime);
  1334.             gExpectedTickTime := gExpectedTickTime - (longint(kNumberOfTocks) * kTicksPerTock * gSecondsPerTick);
  1335.             
  1336.             (* Now loop through each saved sample, calling Tock for each one. *)
  1337.             for i := 0 to kNumberOfTocks - 1 do begin
  1338.                 SecondsToDate(gExpectedTickTime, tmpDatetime);
  1339.                 Tock(    ActivityLogHandle(savedActivity)^^.recentActivity[i].activityTicks,
  1340.                             ActivityLogHandle(savedActivity)^^.recentActivity[i].totalTicks,
  1341.                             tmpDatetime, false
  1342.                         );
  1343.                 (* Advance gExpectedTickTime by a Tock's worth of time. *)
  1344.                 gExpectedTickTime := gExpectedTickTime + kTicksPerTock * gSecondsPerTick;
  1345.             end; (* for *)
  1346.  
  1347.             (* Now move gExpectedTickTime back 14 minutes, so that we can start afresh. *)
  1348.             gExpectedTickTime := gExpectedTickTime - (kTicksPerTock - 1) * gSecondsPerTick;
  1349.             
  1350.             SetPort(gMainWindow);
  1351.             InvalRect(gDestRect);
  1352.             
  1353.             {$ifc false}
  1354.                 DebugStr(stringof('gExpectedTickTime = ', DateTimeToString(gExpectedTickTime)));
  1355.             {$endc}
  1356.         end; (* if *)
  1357.         
  1358.         (* Clean up. *)
  1359.         if savedActivity <> nil then begin
  1360.             HUnlock(savedActivity);
  1361.         end; (* if *)
  1362.         SetPort(oldWorld);
  1363.     end; (* InitApplication *)
  1364.  
  1365.     var
  1366.         junkBool : Boolean;
  1367.         event : EventRecord;
  1368.         currentTime : longint;
  1369.         delay : longint;
  1370. begin
  1371.     (* Init the universe. *)
  1372.     InitToolbox;
  1373.     InitApplication;
  1374.     
  1375.     (* The main event loop! *)
  1376.     repeat
  1377.         
  1378.         (* Calculate delay, the number of ticks to between the current time and the
  1379.                 time for the next tick.  This is what we pass as the sleep parameter
  1380.                 to WaitNextEvent.
  1381.         *)
  1382.         delay := (gExpectedTickTime - MyGetDateTime) * 60;
  1383.         if delay < 0 then begin
  1384.             delay := 0;
  1385.         end; (* if *)
  1386.         junkBool := WaitNextEvent(everyEvent, event, delay, nil);
  1387.  
  1388.         HandleEvent(event);
  1389.  
  1390.         (* Check whether something completely wacky has happened to the current time.
  1391.                 If so, clear the activity graph.
  1392.         *)        
  1393.         currentTime := MyGetDateTime;
  1394.         if abs(currentTime - gExpectedTickTime) >= (kNumberOfTocks * kTicksPerTock * gSecondsPerTick) then begin
  1395.             ClearActivityLog;
  1396.         end; (* if *)
  1397.  
  1398.         (* Handling any pending Ticks. *)
  1399.         repeat
  1400.             currentTime := MyGetDateTime;
  1401.             if currentTime >= gExpectedTickTime then begin
  1402.                 Tick(currentTime);
  1403.             end; (* if *)
  1404.         until currentTime < gExpectedTickTime;
  1405.  
  1406.         (* Save the activity log if necessary. *)
  1407.         if gNeedsSaving then begin
  1408.             if HardDiskSpinning then begin
  1409.                 SaveActivityLog;
  1410.             end; (* if *)
  1411.         end; (* if *)
  1412.         
  1413.     until gQuitNow;
  1414. end. (* WorkinTooHard *)
  1415.